﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace ChatDLuXService
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Chat_Service" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select Chat Service.svc or Chat Service.svc.cs at the Solution Explorer and start debugging.
    public class Chat_Service : IChat_Service
    {
        Utilities util = new Utilities();

        /***
         * Calls spRegister stored procedure which storres username, randomly generated salt
         * and hashed password in the database (tblUser)
         * ***/
        public void Register(string Username, string Password, string Email)
        {
            byte[] salt = util.GenerateSalt(10);
            byte[] hash = util.GenerateHash(Password, salt);

            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("@Username", Username),
                new SqlParameter("@Salt", salt),
                new SqlParameter("@Password", hash),
                new SqlParameter("@Email", Email)
            };
            util.ExecuteProcedure("chat_dev.spRegister", parameters);
        }

        /***
         * Gets the salt for username provided, hashes pasword input with the retrieved salt
         * and calls the spGetLogin stored procedure
         * ***/
        public DataTable GetLogin(string Username, string Password)
        {
            byte[] salt = GetSalt(Username);
            byte[] hash = util.GenerateHash(Password, salt);

            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("@Username", Username),
                new SqlParameter("@Password", hash)
            };
            DataTable dt = util.ReadData("chat_dev.spGetLogin", parameters);
            return dt;
        }

        /***
         * Gets the salt value as a byte array associated with the username provided
         * ***/
        public byte[] GetSalt(string Username)
        {
            byte[] salt = new byte[0];
            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("@Username", Username)
            };
            DataTable dt = util.ReadData("chat_dev.spGetSalt", parameters);
            foreach (DataRow r in dt.Rows)
            {
                salt = (byte[])r["Salt"];
            }
            return salt;
        }

        /***
         * Checks if a Username is already in use
         * ***/
        public bool UserExists(string Username)
        {
            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("@Username", Username)
            };
            bool result = util.CheckExists("chat_dev.spUserExists", parameters);

            return result;
        }

        /***
         * Checks if a Email is already in use
         * ***/
        public bool EmailExists(string Email)
        {
            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("@Email", Email)
            };
            bool result = util.CheckExists("chat_dev.spEmailExists", parameters);

            return result;
        }

        /***
         * Login service that will return a populated User object if login is valid
         * ***/
        public Model.User Login(string Username, string Password)
        {
            Model.User user = new Model.User(Username, Password);

            if (user.PkId > 0) // User exists in db
            {
                return user;
            }
            else
            {
                return null;
            }
        }

        /****
         * Check if this room exists in the database
         ****/
        public bool RoomExists(string RoomName)
        {
            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("@RoomName", RoomName)
            };

            bool result = util.CheckExists("chat_dev.spRoomExists", parameters);//TODO this needs proc written

            return result;
        }

        public void CreateRoom(string Roomname, string RoomDescription)
        {            
            //sql parameters name, description
            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("@RoomName", Roomname),
                new SqlParameter("@Description", RoomDescription)
            };
            util.ExecuteProcedure("chat_dev.spCreateRoom", parameters);
        }
        /***
         * Get room details from database
         * ***/
        public DataTable GetRoomDetails(string RoomName)
        {
            SqlParameter[] parameters = new SqlParameter[]
            {
                new SqlParameter("RoomName", RoomName)
            };
            DataTable dt = util.ReadData("chat_dev.spGetRoomDetails", parameters);
            return dt;
        }

        /***
         * Gets all roms from database
         * ***/
        public DataTable GetRooms()
        {
            DataTable dt = util.ReadData("chat_dev.spGetRooms", null);
            return dt;
        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class BroadcastorService : IBroadcastorService
    {
        private static Dictionary<long, Model.User> allClients = new Dictionary<long, Model.User>(); // holds a list of all currently connected cients as user objects
        private static Dictionary<long, Model.Room> rooms = new Dictionary<long, Model.Room>(); // holds a list of all active rooms.
        private static object locker = new object(); // Used to synchronize the access to variable clients among multiple threads

        /***
         * This method is called when a user logs in, it will pass in the client ID so the 
         * service can add the user to the list of active clients
         * ***/
        public void RegisterClient(Model.User user)
        {
            if (user.PkId > 0)
            {
                try
                {
                    // unique callback channel for the client
                    IBroadcastorCallBack callback = OperationContext.Current.GetCallbackChannel<IBroadcastorCallBack>();
                    user.callback = callback;
                    lock (locker)
                    {
                        // remove the old client
                        if (allClients.Keys.Contains(user.PkId))
                            allClients.Remove(user.PkId);
                        allClients.Add(user.PkId, user);
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }

        public void RegisterRoom(Model.Room room)
        {
            if(room.RoomID > 0) // Room exists
            {
                
            }
        }

        /***
         * Clients will call this method to notify the service about an event. after
         * recieving the service will loop through all active clients, connect through
         * the callback channel, and broadcast the event to the client
         * ***/
        public void NotifyServer(EventDataType eventData)
        {
            lock (locker)
            {
                var inactiveClients = new List<long>();
                foreach (var client in allClients)
                {
                    if (client.Key != eventData.ClientID)
                    {
                        try
                        {
                            client.Value.callback.BroadcastToClient(eventData);
                        }
                        catch (Exception ex)
                        {
                            inactiveClients.Add(client.Key);
                        }
                    }
                }

                if (inactiveClients.Count > 0)
                {
                    foreach (var client in inactiveClients)
                    {
                        allClients.Remove(client);
                    }
                }
            }
        }
    }
}
